package cn.frontpay.payment.wechat.support;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import org.apache.commons.lang3.StringUtils;

import cn.frontpay.payment.wechat.support.utils.json.JsonUtils;

public class EPayDemo {
	
	 private static String cerFilePath = "//C://EAccount.cer";
	 private static String pfxFilePath = "//c://EAccount.pfx";
    
    public static void main(String[] args) throws Exception {
    	
    	
    	//RequestHandler();
    	
    	RespondeHandler();
    }
    
    //外部请求平台签名流程DEMO
    public static void RequestHandler() throws Exception
    {
        /** ================ 1.构造请求数据结构体 ================ */
        Map<String, String> data = new HashMap<>();
        //报文头
        data.put("channelNo", "L000");
        data.put("lang", "zh_CN");
        data.put("optType", "FT_S_P104");
        data.put("publicKeyNo", "0001");
        data.put("timestamp", "150043065761");
        data.put("randStr", "89aa15cbb5e49c71f888625212bebd79");
        data.put("version", "1.0");
        //报文体
        //业务数据 bizdata=urlencode(base64(业务数据 json 格式))
        String bizdata = "eyJPcmRlck5vIjoxNTAwNDMwNjU3LCJTaG9wSWQiOiIwMjYwNiIsIkRldmljZUlkIjoiIiwiQ2hhbm5lbCI6IjEyMDMiLCJBbW91bnQiOjE1NSwiU3ViamVjdCI6Iua1i%2Bivlea1i%2BivlSIsIkRpc2NvdW50QW1vdW50IjoiMjAwLjAwIiwiQ2FsbGJhY2tVcmwiOiJodHRwczovL3hseS54aWFyaWt1aS5jbi9Ib21lL0NhcnQvY2FsbGJhY2siLCJSZWRpcmVjdFVybCI6Imh0dHBzOi8veGx5LnhpYXJpa3VpLmNuL0hvbWUvVXNlci9vcmRlciJ9";
        data.put("data",bizdata);

        /** ================ 2.对原始报文排序、合并、SHA1处理========= */
        String data2 = SignUtil.sign(data);
        
        /** ================ 3.报文签名================ */
        //EAccount123 为密码，不同私钥密码不一样。
        String sign = DigitalCertificateUtils.signDataWithPfxFilePath(data2, "EAccount123",pfxFilePath,SignatureConversionType.Hexadecimal);
        //放入请求数据结构体
        data.put("sign", sign);
        
        /** ================ 4.发起请求================ */
        //用JSON序列化工具将data结构体转成 JSON
        String json= JsonUtils.toJson(data);
        
        System.out.print(json);
        
        //用httpClient POST data 提交支付平台 https/http://openapi.frontpay.cn/openapi.do
    	
    }
    
    //响应平台验签流程DEMO
    public static void RespondeHandler() throws Exception
    {
    	//模拟返回的JSONO
    	/*  
    	{
   		      "code" : "10700",
    		  "message" : "没有找到商户",
    		  "timestamp" : 150043065761,
    		  "randStr" : "89aa15cbb5e49c71f888625212bebd79",
    		  "sign" : "0896ca3cbcb89900389864c132d06e6428fc9468578a56b68456ec206cfbac199556cfd0daccd198a4e5a858416251d2cdc6a4b133df161a658d13b6712c03c84e4535eaf616e42b20109c149a398db0e70175de58f3efeb17190a9865ba8240506d679baea6a7bc7f5c558bfcbd2064a5955264cc59a266b3a685d5d14afb41",
    	}*/
    	
        /** ================  1.接收平台返回报文结构  ================ */
        Map<String, String> params = new HashMap<String, String>();
        params.put("code", "10700");
        params.put("message", "没有找到商户");
        params.put("randStr", "89aa15cbb5e49c71f888625212bebd79");
        params.put("data", null);
        params.put("timestamp", "150043065761");
        params.put("version", "1.0");
        
        /** ================ 2.对原始报文排序、合并、SHA1处理================ */
        String data = SignUtil.sign(params);
        
        /** ================ 3.用渠道私钥签名================ */
        String signByPfx = DigitalCertificateUtils.signDataWithPfxFilePath(data, "EAccount123",pfxFilePath,SignatureConversionType.Hexadecimal);
        
        /** ================ 3.加载支付平台公钥数据  ================ */
        // 平台公钥
        String publicKey = "TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF4MGRFQjFkZXppZVk5SzF2MEhaR3hJN0ZLejZLR0VhdkdaM2NQWWs1SmMwYWlBRFlvbHJjL2wzTjRWL0pJUm0zRmJ2K1RDQk02eE1ZcDE4MUg4S0VndEs1VzBPOWMvTVorQ1lxYzVmdjdVSFdvYUpISWk0bjJ4elJGbllXVEhXRU9vakdmVHJhbXZ0aEdzanJlMzg4MEdPVUl3eEQ3QzZheWswNklRcHk3UkNPZkFCbklhYmxsWWcwVTB2dStseHpMc0djME9sSWdIWnRMbUZNTnlURzk5Sm5kK0EzUGR0b0VQbmwzTTRiQnZsZ0kyZndWWVVQcWV0SW12NmZ3OGxiSTB0RERTbHo4YkJtRTRvUlpzampiUkxzV0dmMnFwaFZaQ1hwZTBlRTE1UmRaMnFRQW1TU3dzZkpDWTBLZU1JVnhUWnpzdVhBRjE1OWdFVXIrK09tZXdJREFRQUI=";
        byte[] publicKeyPemByte = Base64Util.decodeToBytes(publicKey);
        
        /** ================ 4.验签  ================ */
        //用平台公钥publicKeyPemByte、signByPfx、data进行验签。
        //验签逻辑分2种，1：私钥为 pem格式，2：私钥为 cer格式,根据实际情况调用不同逻辑。
        // 验签 （pem 格式验签）
        //boolean verifyResultByPem = DigitalCertificateUtils.verifyWithPemBytes(data, signByPfx, publicKeyPemByte, SignatureConversionType.Hexadecimal);
        // 读取cer文件验签
        boolean verifyResultByCer = DigitalCertificateUtils.verifyWithCerFilePath(data,signByPfx, cerFilePath, SignatureConversionType.Hexadecimal);
        
        /** ================ 4.根据验签结果，调用方进行后续业务逻辑处理  ================ */
        
        System.out.print("verify result:"+verifyResultByCer);
    }
    
    public static class DigitalCertificateUtils {
        
        private static String algorithm = "SHA1withRSA";

        private static String encodingType = "UTF-8";
        
        public static boolean verifyWithPemBytes(String data, String signedData, byte[] pem,
                SignatureConversionType signatureConversionType) throws Exception {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(pem);
            InputStream pemInputStream = (InputStream) byteArrayInputStream;
            boolean verify = verifyWithPemStream(data, signedData, pemInputStream, signatureConversionType);
            pemInputStream.close();
            return verify;
        }
        
        public static boolean verifyWithPemStream(String data, String signedData, InputStream pem,
                SignatureConversionType signatureConversionType) throws Exception {
            byte[] bytesData = data.getBytes(DigitalCertificateUtils.encodingType);
            byte[] bytesSignedData = getConversionByteWithString(signedData, signatureConversionType);
            return verifyWithPemStream(bytesData, bytesSignedData, pem);
        }
        
        public static boolean verifyWithPemStream(byte[] data, byte[] signedData, InputStream pem) throws Exception {
            Signature instance = Signature.getInstance(algorithm);
            PublicKey publicKey = SecurityUtils.getPublicKeyFromPem(pem);
            instance.initVerify(publicKey);
            instance.update(data);
            return instance.verify(signedData);
        }
        
        private static byte[] getConversionByteWithString(String dataStr, SignatureConversionType signatureConversionType)
                throws Exception {
            switch (signatureConversionType) {
            case Base64WithUtf8:
                return Base64Util.decodeToBytes(dataStr);
            case Hexadecimal:
                return Byte2Hex.hex2byte(dataStr);
            default:
                throw new Exception(
                        "转换为byte[]类型时候,判断的枚举类型SignatureConversionType没处理,没有处理的类型是:" + signatureConversionType.toString());
            }
        }
        
        private static String getConversionStringWithByte(byte[] dataByte, SignatureConversionType signatureConversionType)
                throws Exception {
            switch (signatureConversionType) {
            case Base64WithUtf8:
                return Base64Util.encode(dataByte);
            case Hexadecimal:
                return Byte2Hex.byte2Hex(dataByte);
            default:
                throw new Exception(
                        "转换为字符串类型时候,判断的枚举类型SignatureConversionType没处理,没有处理的类型是:" + signatureConversionType.toString());
            }
        }
        
        public static String signDataWithPfxFilePath(String data, String password, String pfxFilePath,
                SignatureConversionType signatureConversionType) throws Exception {
            FileInputStream pfxInputStream = new FileInputStream(pfxFilePath);
            String sign = DigitalCertificateUtils.signDataWithPfxStream(data, password, pfxInputStream, signatureConversionType);
            pfxInputStream.close();
            return sign;
        }
        
        public static String signDataWithPfxStream(String data, String password, InputStream pfxStream,
                SignatureConversionType signatureConversionType) throws Exception {
            return getConversionStringWithByte(signDataWithPfxStreamToByteArray(data, password, pfxStream),
                    signatureConversionType);
        }
        
        public static byte[] signDataWithPfxStreamToByteArray(String data, String password, InputStream pfxStream)
                throws Exception {
            Signature signature = Signature.getInstance(DigitalCertificateUtils.algorithm);
            PrivateKey privateKey = getPrivateKeyFromPfx(pfxStream, password);
            signature.initSign(privateKey);
            signature.update(data.getBytes(DigitalCertificateUtils.encodingType));
            return signature.sign();
        }
        
        public static PrivateKey getPrivateKeyFromPfx(InputStream inputStream, String password) throws Exception {
            KeyStore store = KeyStore.getInstance("PKCS12");
            store.load(inputStream, password.toCharArray());
            @SuppressWarnings({ "rawtypes" })
            Enumeration aliases = store.aliases();
            String alias = (String) aliases.nextElement();
            return ((PrivateKey) store.getKey(alias, password.toCharArray()));
        }
        
        
        /**
         * 使用cer检验签名
         */
        public static boolean verifyWithCerFilePath(String data, String signedData, String cerFilePath,
                SignatureConversionType signatureConversionType) throws Exception {
            FileInputStream cerInputStream = new FileInputStream(cerFilePath);
            boolean verify = DigitalCertificateUtils.verifyWithCerStream(data, signedData, cerInputStream, signatureConversionType);
            cerInputStream.close();
            return verify;
        }
        
        /**
         * 使用cer检验签名
         */
        public static boolean verifyWithCerStream(String data, String signedData, InputStream cerInputStream,
                SignatureConversionType signatureConversionType) throws Exception {
            byte[] bytesData = data.getBytes(DigitalCertificateUtils.encodingType);
            byte[] bytesSignedData = getConversionByteWithString(signedData, signatureConversionType);
            return DigitalCertificateUtils.verifyWithCerStream(bytesData, bytesSignedData, cerInputStream);
        }
        
        /**
         * 使用cer检验签名
         */
        public static boolean verifyWithCerStream(byte[] data, byte[] signedData, InputStream cer) throws Exception {
            Signature instance = Signature.getInstance(algorithm);
            PublicKey publicKey = SecurityUtils.getPublicKeyFromCer(cer);
            instance.initVerify(publicKey);
            instance.update(data);
            return instance.verify(signedData);
        }
    }

    public static class SignUtil {
        
        /**
         * 参数排序参数后合并 —> SHA1 —> 16进制 —> 小写
         * @param paramValues
         * @return
         */
        public static String sign(Map<String, String> paramValues)  {
            try {
                StringBuilder sb = new StringBuilder();
                List<String> paramNames = new ArrayList<String>(paramValues.size());
                paramNames.addAll(paramValues.keySet());
                Collections.sort(paramNames);
                for (String paramName : paramNames) {
                    if(StringUtils.isNotBlank(paramValues.get(paramName)))
                    sb.append(paramName).append("=").append(paramValues.get(paramName)).append("&");
                }
                byte[] sha1Digest = getSHA1Digest(sb.toString().substring(0,sb.lastIndexOf("&")));
                return Byte2Hex.byte2Hex(sha1Digest).toLowerCase();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        
        /**
         * 给字符串做SHA1加密
         * @param data
         * @return
         * @throws IOException
         */
        private static byte[] getSHA1Digest(String data) throws IOException {
            byte[] bytes = null;
            try {"".toUpperCase().toLowerCase();
                MessageDigest md = MessageDigest.getInstance("SHA-1");
                bytes = md.digest(data.getBytes("UTF-8"));
            } catch (GeneralSecurityException gse) {
                throw new IOException(gse.getMessage());
            }
            return bytes;
        }

    }
    
    public static class SecurityUtils {
        public static PublicKey getPublicKeyFromPem(InputStream pem) throws Exception {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(4096);
            int tmpInt = 0;
            while (-1 != (tmpInt = pem.read())) {
                byteArrayOutputStream.write(tmpInt);
            }
            byte[] bytes = byteArrayOutputStream.toByteArray();
            return getPublicKeyFromPem(bytes);
        }
        
        public static PublicKey getPublicKeyFromPem(byte[] pem) throws Exception {
            String publicKey = new String(pem);
            publicKey = publicKey.replace("-----BEGIN PUBLIC KEY-----", "");
            publicKey = publicKey.replace("-----END PUBLIC KEY-----", "");
            byte[] encodedKey = Base64Util.decodeToBytes(publicKey);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
        }
        
        /**
         * 获取公钥，inputStream不会被关闭，请手动关闭
         */
        public static PublicKey getPublicKeyFromCer(InputStream inputSteam) throws Exception {
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            return (factory.generateCertificate(inputSteam).getPublicKey());
        }
    }
    
    public static class Base64Util {

        private static char[] codec_table = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
                'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
                'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
                't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                '+', '/' };
        
        private static String defaultCharset = "UTF-8";

        /**
         * Base64解码
         * 
         * @param content 字符串
         * @return String
         */
        public static String decode(String content) {
            return decode(content, defaultCharset);
        }

        public static String decode(String content,String charset) {
            try {
                byte[] b = new BASE64Decoder().decodeBuffer(content);
                return new String(b, charset);
            } catch (IOException e) {
                return "";
            }
        }

        /**
         * Base64解码
         * 
         * @param content 字符串
         * @return byte[]
         */
        public static byte[] decodeToBytes(String content) {
            try {
                return new BASE64Decoder().decodeBuffer(content);
            } catch (IOException e) {
                return new byte[] {};
            }
        }

        @Deprecated
        public static String encode(byte[] content) {
            try {
                String result = new BASE64Encoder().encode(content);
                
                result = result.replace("\r", "");
                result = result.replace("\n", "");
                
                return result;
                //return new BASE64Encoder().encode(content);
            } catch (Exception e) {
                return "";
            }
        }
        
        public static String encodeForLong(byte[] a) {
            int totalBits = a.length * 8;
            int nn = totalBits % 6;
            int curPos = 0;// process bits
            StringBuffer toReturn = new StringBuffer();
            while (curPos < totalBits) {
                int bytePos = curPos / 8;
                switch (curPos % 8) {
                case 0:
                    toReturn.append(codec_table[(a[bytePos] & 0xfc) >> 2]);
                    break;
                case 2:

                    toReturn.append(codec_table[(a[bytePos] & 0x3f)]);
                    break;
                case 4:
                    if (bytePos == a.length - 1) {
                        toReturn.append(codec_table[((a[bytePos] & 0x0f) << 2) & 0x3f]);
                    } else {
                        int pos = (((a[bytePos] & 0x0f) << 2) | ((a[bytePos + 1] & 0xc0) >> 6)) & 0x3f;
                        toReturn.append(codec_table[pos]);
                    }
                    break;
                case 6:
                    if (bytePos == a.length - 1) {
                        toReturn.append(codec_table[((a[bytePos] & 0x03) << 4) & 0x3f]);
                    } else {
                        int pos = (((a[bytePos] & 0x03) << 4) | ((a[bytePos + 1] & 0xf0) >> 4)) & 0x3f;
                        toReturn.append(codec_table[pos]);
                    }
                    break;
                default:
                    break;
                }
                curPos += 6;
            }
            if (nn == 2) {
                toReturn.append("==");
            } else if (nn == 4) {
                toReturn.append("=");
            }
            return toReturn.toString();

        }
    }

    /**
     * 签名转换类型(转码方式)
     *
     */
    public enum SignatureConversionType {
        /**
         * utf-8编码的bas64
         */
        Base64WithUtf8,
        /**
         * 十六进制
         */
        Hexadecimal
    }
    
    public static class Byte2Hex {

        /**
         * 
         * 字节转换为 16 进制字符串
         * 
         * @param b
         *          字节
         * @return
         */
        public static String byte2Hex( byte b ) {
            String hex = Integer.toHexString(b);
            if ( hex.length() > 2 ) {
                hex = hex.substring(hex.length() - 2);
            }
            while ( hex.length() < 2 ) {
                hex = "0" + hex;
            }
            return hex;
        }


        /**
         * 
         * 字节数组转换为 16 进制字符串
         * 
         * @param bytes
         *          字节数组
         * @return
         */
        public static String byte2Hex( byte[] bytes ) {
            Formatter formatter = new Formatter();
            for ( byte b : bytes ) {
                formatter.format("%02x", b);
            }
            String hash = formatter.toString();
            formatter.close();
            return hash;
        }
        
        /**
         * 将16进制字符串转换为字节
         * @param strhex
         * @return
         */
        public static byte[] hex2byte(String strhex) {
            if (strhex == null)
                return null;

            int l = strhex.length();
            if (l % 2 == 1)
                return null;

            byte[] b = new byte[l / 2];
            for (int i = 0; i != l / 2; ++i)
                b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2),
                        16);

            return b;
        }
    }
    
    public static class SHA1 {
        public static String ALGORITHM = "SHA1";
        
        public static String sha1ReturnString(String data) {
            StringBuilder sha1Str = new StringBuilder();
            try {
                MessageDigest sha1 = MessageDigest.getInstance(SHA1.ALGORITHM);
                byte[] bytes = sha1.digest(data.getBytes("UTF-8"));
                for (int i = 0; i < bytes.length; i++) {
                    sha1Str.append(Byte2Hex.byte2Hex(bytes[i]));
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
            return sha1Str.toString();
        }
    }
    
}


